using UnityEngine;
using System.Collections;
using Decision_Tree;
using DebugTools;

public class GameController: MonoBehaviour {
	private const int guiWidth = 400;
	private const int guiXOffset = 20;
	private const int guiYOffset = 20;
	private const int guiYSeperator = 10;
	private const int guiYSpace = 1;
	private const int roundTime = 100;
	public bool neighbours_enabled = false;
	private float aStarCPUUsage = 0.2f;
	private float agentBreakaway = 0.00075f;
	private ArrayList agents = new ArrayList ();
	private float boidFleeBlendWeight = 0.5f;
	private LayerMask boidMask = 16;
	private float boidWanderBlendWeight = 0.5f;
	private int cameraFollowing = 0;
	private CameraScript cameraScript;
	private bool collisionEnabled = true;
	private bool drawAStarNodes = false;
	private ArrayList energyPickups = new ArrayList ();
	private int goalNumberOfAgents;
	private int goalNumberOfHerders;
	private bool guiEnabled = true;
	private int guiYCurrentOffset = guiYOffset;
	private ArrayList herders = new ArrayList ();
	private bool knowledgeEnabled = true;
	private Maze maze;
	private int num_herders = 3;
	private int numberOfAgents = 2;
	private bool prevKnowledgeEnabled = true;
	private float roundEndTime;
	private ArrayList spawnArea;
	private ArrayList starPickups = new ArrayList ();
	
	private LineRenderer pathRenderer; 


	public float getAStarCPUUsage() {
		return aStarCPUUsage;
	}

	public ArrayList getAgents() {
		return agents;
	}

	public bool getDrawAStarNodes() {
		return drawAStarNodes;
	}

	public ArrayList getEnergyPickups() {
		return energyPickups;
	}

	public ArrayList getHerders() {
		return herders;
	}

	public bool getKnowledgeEnabled() {
		return knowledgeEnabled;
	}

	public Maze getMaze() {
		return maze;
	}

	public Vector3 getNearestExit(Vector3 position) {
		float shortestDist = -1.0f;
		Vector3 shortestPos = position;

		foreach ( Vector3 finish in maze.getExits() )
		{
			if (Vector3.Distance(finish, position) < shortestDist || shortestDist < 0)
			{
				shortestDist = Vector3.Distance(finish, position);
				shortestPos = finish;
			}
		}
		return shortestPos;
	}

	public float getRemaingRoundTime() {
		return (roundEndTime - Time.timeSinceLevelLoad);
	}

	void OnGUI() {
		guiYCurrentOffset = guiYOffset;
		addGUIText("Time Left in round " + (int)(roundEndTime - Time.timeSinceLevelLoad) + " Seconds", 1);
		guiEnabled = addGUIToggle("Enable GUI", guiEnabled);

		if (!guiEnabled) return;

		knowledgeEnabled = addGUIToggle("Enable Agent Knowledge", knowledgeEnabled);

		if(prevKnowledgeEnabled != knowledgeEnabled)
		{
			rebuildAgentsDecisionTrees();
			prevKnowledgeEnabled = knowledgeEnabled;
		}

		addGUISeperator();

		addGUIText("A Star CPU Usage Level: " + aStarCPUUsage, 1);
		aStarCPUUsage = addGUIHSlider(aStarCPUUsage, 0.0f, 1.0f);

		drawAStarNodes = addGUIToggle("Draw A Star Nodes", drawAStarNodes);

		if (addGUIButton("View whole maze"))
		{
			cameraScript.resetView();
			cameraFollowing = -1;
		}

		if (cameraFollowing	>= 0)
		{
			addGUIText("Following agent Number : " + (cameraFollowing+1), 1);

			AgentDecisionTree agentDecisionTree = (agents[cameraFollowing] as AgentBehaviourScript).getAgentDecisionTree();
			if (agentDecisionTree != null)
				addGUIText("Agent Action: " + agentDecisionTree.getActionAsString(), 1);

			if ((agents[cameraFollowing] as AgentBehaviourScript).getCurrentPlan() != null)
				addGUIText("Agent Plan: " + (agents[cameraFollowing] as AgentBehaviourScript).getCurrentPlan().ToString(), 2);
			else
				addGUIText("Agent Plan: None" , 2);

			if ((agents[cameraFollowing] as AgentBehaviourScript).getSearchPlan() != null)
				addGUIText("Agent Search: " + (agents[cameraFollowing] as AgentBehaviourScript).getSearchPlan().ToString(), 2);
			else
				addGUIText("Agent Search: None" , 2);


			addGUIText("Agent energy: " + (agents[cameraFollowing] as AgentBehaviourScript).getEnergy(), 1);
			addGUIText("Path distance to closest herder: " + (agents[cameraFollowing] as AgentBehaviourScript).getDistanceToNearestHerder(), 1);
			addGUIText("Path distance to closest  energy: " + (agents[cameraFollowing] as AgentBehaviourScript).getDistanceToNearestEnergy(), 1);
			addGUIText("Path distance to goal: " + (agents[cameraFollowing] as AgentBehaviourScript).getPathDistanceToGoal(), 1);

			addGUISeperator();

			AgentBehaviourScript curAgent = (agents[cameraFollowing] as AgentBehaviourScript);
			addGUIText("bHasFood: " + curAgent.brain.valueForAttribute(Attribute.bHasFood), 1);
			addGUIText("bCloseToHerder: " + curAgent.brain.valueForAttribute(Attribute.bCloseToHerder), 1);
			addGUIText("bCloseToFood: " + curAgent.brain.valueForAttribute(Attribute.bCloseToFood), 1);
			addGUIText("bCloseToGoal: " + curAgent.brain.valueForAttribute(Attribute.bCloseToGoal), 1);
			addGUIText("bTimeLeft: " + curAgent.brain.valueForAttribute(Attribute.bTimeLeft), 1);

			addGUISeperator();

			if (addGUIButtonLeft("Follow previous agent"))
			{
				cameraScript.resetView();
				cameraFollowing--;
				if (cameraFollowing < 0) cameraFollowing = 0;
			}

			if (addGUIButtonRight("Follow next agent"))
			{
				cameraScript.resetView();
				cameraFollowing++;
				if (cameraFollowing > agents.Count - 1) cameraFollowing = agents.Count - 1;
			}
		} else {
			if (addGUIButton("Follow first agent"))
			{
				cameraScript.resetView();
				cameraFollowing = 0;
			}
		}

		addGUISeperator();

		addGUIText("Energy pieces left: " + energyPickups.Count, 1);
	}

	void Start() {
		cameraScript = ((CameraScript)GameObject. FindWithTag("MainCamera").GetComponent("CameraScript"));
		
		GameObject renderer = new GameObject("Path Renderer"); 
		renderer.AddComponent("LineRenderer");
		renderer.renderer.material.shader = Shader.Find("Particles/Additive");
		renderer.renderer.enabled = false;
		renderer.renderer.material.mainTexture = (Texture)Resources.Load("Scripts/Herder/laserbw3");
		pathRenderer = (LineRenderer)renderer.GetComponent("LineRenderer");	
		pathRenderer.SetWidth(3.0f, 3.0f);	
		//lineRender.SetVertexCount(2);
		pathRenderer.SetColors(new Color(0.0f, 1.0f, 0.0f, 1.0f), new Color(1.0f, 0.0f, 0.0f, 1.0f));
		

		maze = new Maze();
		maze.createMaze(15, 80, 80);
		spawnArea = maze.getSpawnArea();

		goalNumberOfAgents = numberOfAgents;
		goalNumberOfHerders = num_herders;

		int currentNumberOfHerders = 0;

		//Look for things to spawn
		// TODO : put in restart round and remove all objects too
		while (currentNumberOfHerders != goalNumberOfHerders)
		{
			for (int j = 0; j < maze.getHeight(); j++)
			{
				for (int i = 0; i < maze.getWidth(); i++)
				{
					MazeLocation location = new MazeLocation(i,j);
					if (maze.getMazeSquare(location).isEnergy())
						addEnergy(maze.getMazePosition(location));

					if (maze.getMazeSquare(location).isHerderSpawnPoint() && currentNumberOfHerders < goalNumberOfHerders)
					{
						addHerder(maze.getMazePosition(location));
						currentNumberOfHerders++;
					}
				}
			}
		}

		//Instantiate a group of agents and store them in a list
		for (int i = 0; i < numberOfAgents; i++)
		{
			addAgent();
		}

		restartRound();
	}

	void Update() {
		if (roundEndTime < Time.timeSinceLevelLoad)
				restartRound();
		if (cameraFollowing	< 0)
		{
			cameraScript.setOverview((maze.getWidth()/2) * maze.getCellSize(), (maze.getHeight()/2) * maze.getCellSize());
		}else
		{
			if (agents.Count > 0)
				cameraScript.lookAtOrbit(((AgentBehaviourScript)agents[cameraFollowing]).getPosition(),
																 ((AgentBehaviourScript)agents[cameraFollowing]).transform.rotation.eulerAngles.y);
		}
		
		if (cameraFollowing	>= 0)
		{
			pathplanner pathPlanner = null;
			if (((AgentBehaviourScript)agents[cameraFollowing]).getCurrentPlan() != null)
				pathPlanner = ((AgentBehaviourScript)agents[cameraFollowing]).getCurrentPlan().getPathPlanner();
			if (pathPlanner	!= null)
			{
				pathPlanner.setup_line_renderer(pathRenderer);
				pathRenderer.renderer.enabled = true;
			}
			else
			{
				pathRenderer.renderer.enabled = false;
			}
		}
	}

	private void addAgent() {
		// Create a new game object and load the model
		GameObject agent = (GameObject)Instantiate(Resources.Load("Prefabs/AgentPrefab"));
		agent.transform.localPosition = new Vector3(0, 0, 0);

		// Note: Changing the scale affects their physics constants
		agent.transform.localScale = new Vector3(15,15,15);

		// steering behaviours are no longer Monobehaviors so they just get created by constructor
		// inside the agent
		agent.AddComponent("AgentBehaviourScript");

		agent.AddComponent("SphereCollider");

		// Add agent to its own collision layer
		agent.layer = boidMask;

		agents.Add(agent.GetComponent("AgentBehaviourScript"));
	}

	private void addEnergy(Vector3 position) {
		GameObject energyPickup = (GameObject)Instantiate(Resources.Load("Prefabs/EnergyPrefab"));
		energyPickup.transform.position = position;
		energyPickup.transform.localScale = new Vector3(15,15,15);
		energyPickup.AddComponent("EnergyPickup");
		energyPickups.Add(energyPickup.GetComponent("EnergyPickup"));
	}

	private bool addGUIButton(string text) {
		const int buttonHeight = 30;
		bool result = GUI.Button(new Rect(guiXOffset, guiYCurrentOffset, guiWidth, buttonHeight), text);
		guiYCurrentOffset += buttonHeight+ guiYSpace;

		return result;
	}

	private bool addGUIButtonLeft(string text) {
		const int buttonHeight = 30;
		bool result = GUI.Button(new Rect(guiXOffset, guiYCurrentOffset, guiWidth/2, buttonHeight), text);
		//guiYCurrentOffset += buttonHeight;
		return result;
	}

	private bool addGUIButtonRight(string text) {
		const int buttonHeight = 30;
		bool result = GUI.Button(new Rect(guiXOffset + guiWidth/2, guiYCurrentOffset, guiWidth/2, buttonHeight), text);
		//only increment on right button
		guiYCurrentOffset += buttonHeight+ guiYSpace;
		return result;
	}

	private float addGUIHSlider(float theValue, float lValue, float rValue) {
		const int hSliderHeight = 25;
		float returnValue = GUI.HorizontalSlider(new Rect(guiXOffset, guiYCurrentOffset, guiWidth, hSliderHeight), theValue, lValue, rValue);
		guiYCurrentOffset += hSliderHeight+ guiYSpace;
		return returnValue;
	}

	private void addGUISeperator() {
		guiYCurrentOffset += guiYSeperator;
	}

	private void addGUIText(string text, int lines) {
		const int labelHeight = 20;

		GUI.Label(new Rect(guiXOffset, guiYCurrentOffset,guiWidth,labelHeight*lines), text);
		guiYCurrentOffset += labelHeight*lines + guiYSpace;
	}

	private bool addGUIToggle(string text, bool toggleValue) {
		const int buttonHeight = 25;
		bool result = GUI.Toggle(new Rect(guiXOffset, guiYCurrentOffset, guiWidth, buttonHeight), toggleValue, text);
		guiYCurrentOffset += buttonHeight+ guiYSpace;

		return result;
	}

	private void addHerder(Vector3 spawnLoc) {
		// Create a new game object and load the model
		GameObject herder = (GameObject)Instantiate(Resources.Load("Prefabs/HerderPrefab"));

		// Note: Changing the scale affects their physics constants
		herder.transform.localScale = new Vector3(7,7,7);


		herder.AddComponent("HerdBehaviour");
		herder.AddComponent("ConvergeBehaviour");
		herder.AddComponent("CircleBehaviour");
		herder.AddComponent("HerderStateMachineScript");
		herder.AddComponent("HerderBehaviourScript");
		herder.AddComponent("SphereCollider");

		//spread out herders
		herder.transform.position = new Vector3(spawnLoc.x, 35.0f, spawnLoc.z);

		herders.Add(herder.GetComponent("HerderBehaviourScript"));
	}

	private void addStar(Vector3 position) {
		GameObject starPickup = (GameObject)Instantiate(Resources.Load("Prefabs/StarPrefab"));
		starPickup.transform.position = position;
		starPickup.transform.localScale = new Vector3(15,15,15);
		starPickup.AddComponent("StarPickup");
		starPickups.Add(starPickup.GetComponent("StarPickup"));
	}

	int getLayerMask() {
		return boidMask;
	}

	void rebuildAgentsDecisionTrees() {
		foreach (AgentBehaviourScript a in agents)
			a.rebuildTree();
	}

	private void removeAgent() {
		GameObject agent = (GameObject)agents[agents.Count-1];
		agents.Remove(agent);
		Destroy(agent);
	}

	private void removeHerder() {
		GameObject herder = (GameObject)herders[herders.Count-1];
		herders.Remove(herder);
		Destroy(herder);
	}

	private void restartRound() {
		roundEndTime = Time.timeSinceLevelLoad + (float)roundTime;
		foreach (AgentBehaviourScript cur in agents)
		{
			cur.spawn();
			//TODO: spawn herders
		}
	}
}
